/**
 * Copyright (C) 2017, hapjs.org. All rights reserved.
 */

import path from 'path'
import fs, { mkdir, writeFile, readFile, stat, readdir, exists } from 'fs'
import { execFile, exec } from 'child_process'
import { promisify } from 'util'
import conf from '../lib/conf'

import {
  mkdirsSync,
  colorconsole,
  getIPv4IPAddress,
  getClientIPAddress
} from '../lib/utils'

const execFilePromise = promisify(execFile)
const mkdirPromise = promisify(mkdir)
const writeFilePromise = promisify(writeFile)
const readFilePromise = promisify(readFile)
const statPromise = promisify(stat)
const readdirPromise = promisify(readdir)
const existsPromise = promisify(exists)

const PROCESS_CACHE_MAP = {}
const LINK_MODE = {
  NULL: 0,
  WIFI: 1,
  ADB: 2
}
const ALLOW_EXT = ['.json', '.js', '.ux']
const PROJECT_KEY_MAP = {}

/**
 * 获取文件目录树
 */
async function getProjectDir (key) {
  let result
  try {
    const pathProject = path.resolve(conf.dirDest, `${key}`)
    const defaultProject = conf.dirBase
    const exists = await existsPromise(pathProject)
    if (exists) {
      result = await getFileInfo(pathProject, {}, path.parse(pathProject).name)
      result.name = PROJECT_KEY_MAP[key] || (PROJECT_KEY_MAP[key] = path.parse(defaultProject).name)
    } else {
      result = await getFileInfo(defaultProject, {}, path.parse(defaultProject).name)
      result.name = PROJECT_KEY_MAP[key] || (PROJECT_KEY_MAP[key] = path.parse(defaultProject).name)
    }
  } catch (e) {
    throw new Error(`获取${key}所在的文件夹出现错误`)
  }
  return result
}

/**
 * 禁止获取的文件条件
 */
function forbiddenFile (file) {
  return file.indexOf('node_modules') > -1 ||
          file.startsWith('\.') ||
          file.indexOf('package.json') > -1 ||
          file.indexOf('package-lock.json') > -1
}

/**
 * 同步读取文件夹目录以及内容
 */
async function getFileInfo (filePath, dir, name) {
  const stats = await statPromise(filePath)
  if (stats && stats.isFile() && ALLOW_EXT.includes(path.extname(name))) {
    dir.name = name
    dir.children = null
    dir.content = await readFilePromise(filePath, 'utf-8')
    return dir
  }
  if (stats && stats.isDirectory()) {
    dir.name = name
    dir.children = []
    const files = await readdirPromise(filePath)
    for (let i = 0; i < files.length; i++) {
      const file = files[i]
      if (forbiddenFile(file)) continue
      const fileDir = path.join(filePath, file)
      const child = await getFileInfo(fileDir, {}, file)
      if (child) {
        dir.children.push(child)
      }
    }
    return dir
  }
}

/**
 * 获取开发者项目中的项目名称
 */
function getProjectName () {
  try {
    const pathProject = process.cwd()
    const pathManifest = path.join(pathProject, 'src', `manifest.json`)
    const contManifest = require(pathManifest)
    return contManifest && contManifest.package || 'Bundle'
  }
  catch (err) {
    colorconsole.error(`### App Server ### 获取项目名称出错：${err.message}`)
  }
}

/**
 * 记录地址
 * @param filePath {String}
 * @param newClient {sn, ip, port}
 *
 */
function recordClient (filePath, newClient) {
  // 目录存在
  const pathParentDir = path.dirname(filePath)

  let tmpJSONData = { clients: [] }
  if (!fs.existsSync(pathParentDir)) {
    mkdirsSync(pathParentDir)
  }
  else if (fs.existsSync(filePath)) {
    tmpJSONData = JSON.parse(fs.readFileSync(filePath).toString())
    if (tmpJSONData.clients instanceof Array) {
      // 过滤相同的记录
      tmpJSONData.clients = tmpJSONData.clients.filter((client) => {
        return client.ip !== newClient.ip || client.port !== newClient.port
      })
      // 保留最后的4条记录，最多记录5条
      while (tmpJSONData.clients.length > 4) {
        tmpJSONData.clients.shift()
      }
    }
  }
  // 写入文件
  tmpJSONData.clients.push(newClient)
  fs.writeFileSync(filePath, JSON.stringify(tmpJSONData))
}

/**
 * 获取可被其它设备访问的服务器地址， 如："http://172.20.1.1:8080"
 * @param port {Number}
 * @return {string}
 */
function getServerAddress (port) {
  return `http://${getIPv4IPAddress()}${port === 80 ? '' : ':' + port}`
}

/**
 * 根据设备sn和ip获取设备的记录
 * @param filePath
 * @param sn
 * @param ip
 * @returns {null}
 */
function getRecordClient (filePath, sn, ip) {
  if (fs.existsSync(filePath)) {
    let { clients } = JSON.parse(fs.readFileSync(filePath).toString())
    clients = clients instanceof Array ? clients : []
    return clients.find((client) => {
      return client.sn === sn && client.ip === ip && client.port
    })
  }
}

/**
 * 获取请求信息
 * @param request
 * @returns {{clientIp: (any|*|string), sn, linkMode}}
 */
function getClientFromRequest (request) {
  const clientIp = getClientIPAddress(request)
  const serverIp = getIPv4IPAddress()
  const sn = request.header['device-serial-number']
  let linkMode = LINK_MODE.NULL
  if (clientIp === '127.0.0.1' && sn) {
    linkMode = LINK_MODE.ADB
  }
  else if (clientIp !== '127.0.0.1' && clientIp !== serverIp) {
    linkMode = LINK_MODE.WIFI
  }
  return { clientIp, sn, linkMode }
}

/**
 * 保存memory至硬盘
 */
async function saveMemoryToDisk (key, fs) {
  try {
    if (fs.root && fs.root.children && fs.root.children[0]) {
      PROJECT_KEY_MAP[key] = fs.root.children[0].name
      fs.root.children[0].name = key
    } else {
      throw new Error(`保存${key}文件树出现错误`)
    }
    await execFilePromise('rm', ['-rf', `${conf.dirDest}/${key}`])
    await buildDir(fs, conf.dirDest)
  } catch (e) {
    throw new Error(`保存${key}文件夹出现错误`)
  }
}

/**
 * 构建文件夹
 */
async function buildDir (fs, baseUrl) {
  for (let i in fs.root.children) {
    await buildFile(baseUrl, fs.root.children[i])
  }
}

/**
 * 构建文件递归调用
 */
async function buildFile (url, file) {
  if (!file.children) {
    await writeFilePromise(`${url}/${file.name}`, file.private.content)
    return
  }
  await mkdirPromise(`${url}/${file.name}`)
  for (let i = 0; i < file.children.length; i++) {
    await buildFile(`${url}/${file.name}`, file.children[i])
  }
}

/**
 * 编译文件
 * @param {string} key 
 */
async function compileToBundle (key) {
  const baseUrl = path.join(conf.dirDest, `${key}`)
  const distUrl = path.join(conf.dirDist, `${key}`)
  if (PROCESS_CACHE_MAP[key]) {
    if (!PROCESS_CACHE_MAP[key].killed) {
      PROCESS_CACHE_MAP[key].kill('SIGINT')
    }
  }
  const result = await new Promise((resolve, reject) => {
    PROCESS_CACHE_MAP[key] = exec(`node webpack.mem.js --baseUrl=${baseUrl} --distUrl=${distUrl}`, {cwd: `${process.cwd()}/src/memfs`}, (error, stdout, stderr) => {
      if (error) {
        reject(error)
      }
      resolve({
        stderr,
        stdout
      })
    })
  })
  return result
}

/**
 * 读取当前文件夹内的rpk
 * @param {string} key 
 */
async function readBundleFile (key) {
  try {
    const projectDist = path.join(conf.dirDist, `${key}`)
    const projectRpk = path.join(projectDist, `com.quickapp.demo.memfs.debug.rpk`)
    const exist = await existsPromise(projectRpk)
    if (exist) {
      return fs.createReadStream(projectRpk)
    } else {
      throw new Error(`${key}所在的文件不存在`)
    }
  } catch (e) {
    throw new Error(`${key}所在的bundle读取错误`)
  }
}

export {
  getProjectName,
  getProjectDir,
  recordClient,
  getServerAddress,
  getClientFromRequest,
  getRecordClient,
  saveMemoryToDisk,
  compileToBundle,
  readBundleFile,
  LINK_MODE,
  PROCESS_CACHE_MAP
}
